perm filename 4RADD.DOC[P,JRA] blob
sn#115471 filedate 1974-08-09 generic text, type C, neo UTF8
COMMENT ⊗ VALID 00011 PAGES
C REC PAGE DESCRIPTION
C00001 00001
C00002 00002 This file holds new, untried sections for 4R.DOC.
C00003 00003 CONTROL STRUCTURES
C00010 00004 PROCEDURES
C00016 00005 CONSTANT VELOCITY MOTION
C00018 00006 TASK STRUCTURES
C00021 00007 LIBRARY ROUTINES
C00025 00008 IMPLEMENTATION OF ON-MONITORS
C00028 00009 INTERPRETER CODE FOR MOTIONS
C00030 00010 THE INTERPRETER SCHEDULER
C00036 00011 DEPROACHES
C00046 ENDMK
C⊗;
This file holds new, untried sections for 4R.DOC.
The oontents are open for discussion.
CONTROL STRUCTURES
RHT has a later version of this.
PROCESS SCHEDULING
Of the three types of processes, joint servos need guaranteed
service, and on-checkers need frequent service. Some on-checkers
listen to hardware interrupts; these should be at interrupt level.
The interpreters may proceed at any pace.
The mechanism used is a time-slot request list. Each time
slot is one millisecond wide. Joint servos and checkers reserve time
slots. A single time slot may be reserved by one servo and any
number of on-monitors.
Different types of processes operate at different priorities:
7. Reserved for future use.
6. Clock, joint servo.
5. Reserved for future use.
4. Hardware and software on-monitors
3. On-monitor bodies.
2. Reseved for future use.
1. Interpreters, scheduler (schedules interpreters).
0. Background.
The clock causes an interrupt every millisecond. The level 6
interrupt handler examines the reservation list for the new slot, and
if there is any servo in that list (servo requests will come first in
the list, so this is a fast check), the handler calls it. The
servo now takes control and executes its function. When it
retruns, or if there was no servo queued, the clock-interrupt
handler causes an interrupt to occur at level 4, and then dismisses.
The level 4 interrupt handler first checks to see if it was awakened
due to a hardware interrupt. If so, it branches to the correct
on-monitor; if not, it picks the first on-monitor in the queue for
this time slot and branches to it. When this monitor returns, the
interrupt handler picks the next in line, and so on until all are
gone, at which time it dismisses. If any hardware interrupts
occurred meanwhile, they now have a chance to be handled. The level 1
interrupt handler picks the first interpreter in line and starts it
going.
When the clock next ticks, it may interrupt a monitor or an
interpreter. In these cases, their interrupts are left pending, and
upon exit from level 6, they will continue where they left off.
PROCEDURES
HAL has only a limited capacity for procedures. All
parameters to a procedure assume the planning value "undefined" at
the conclusion of a procedure call, except those which are declared
as VALUE parameters in the procedure heading, or those stated to be
UNCHANGED in the procedure call. There is no safeguard against the
accidental modification of "unchanged" parameters; to state
"unchanged" is entirely equivalent to an assertion that the parameter
has not changed its value during the execution of the procedure. The
declaration of a procedure is this:
type PROCEDURE name (argument list),
where "type" is any datatype (and is optional), and "argument list"
is a list of parameter names with their types. An example:
SCALAR PROCEDURE LGTH (FRAME F1, F2; VECTOR VALUE V1);
This declares that LGTH is a procedure which returns a scalar, and
takes as arguments two frames and one vector. The vector is not
changed by the procedure.
To call such a procedure:
S1 ← LGTH(FROB, UNCHANGED HOLE, VECT);
This further asserts that HOLE is not changed by the call.
Assertions are essential at the start of a procedure body to
inform the compiler of the values to expect for the various
arguments. Remember that trajectories planned on the basis of highly
inaccurate planning values will not work well.
As a procedure is entered, all variables have planning value
"undefined". Globals may be accessed, but they also have undefined
initial planning value. All variables which have explicit or
implicit assignments to them within a procedure acquire the value
"undefined" at the point directly after the procedure call. In order
to assert that a variable has the same value as before, ASSERT
(UNCHANGED F3).
No modification of the attach structure is allowed inside a
procedure. The compiler (often wrongly) assumes that there are no
attachments involving variables within a procedure; to override this,
ASSERT (ATTACHED F1 F2).
There are four special types of procedure calls: A HAL
program might wish to call a routine coded for the PDP11 or a routine
coded for the PDP10. Likewise, a program on the PDP10 may wish to
control a HAL program, or a routine on the PDP11 may wish to request
some arm motion.
To achieve the first two cases, there exist "external
procedures" in HAL. These are compiled into calls on either routines
in the PDP11 or routines in the runtime PDP10 package. To declare
such a procedure: EXTERNAL PDP10 FRAME PROCEDURE FOO (FRAME A, B;
VECTOR V); This declares the procedure FOO to be a procedure resident
in the runtime PDP10 package, expected to return a frame value, and
taking as arguments two frames and a vector. PDP10 procedures do not
have access to the actual variables sent, since copies are made;
therefore, all arguments to PDP10 procedures are considered to be
VALUE parameters.
It is possible to declare untyped (i.e., statement-like,
instead of expression-like) procedures as well. Replace "PDP10" with
"PDP11" for procedures in the PDP11. PDP11 procedures do have access
to values, and therefore parameters are not automatically considered
to be VALUE.
To achieve the second two cases, there exist "internal
procedures" in HAL. It is not necessary to state from where the
procedure may be called. Internal procedures must be at the top
level of a HAL program. The entire HAL program is considered to be
an untyped procedure without parameters.
CONSTANT VELOCITY MOTION
To cause the arm to quickly achieve a particular velocity,
and to hold it in straight-line motion for a given distance, there is
a special form of the MOVE instruction:
MOVE YELLOW
WITH VELOCITY=V1;
THROUGH T;
FOR DISTANCE=4;
ENDM;
The WITH VELOCITY tells which vector to follow, and how fast. The
THROUGH T tells the compiler where the move expects to end. The FOR
DISTANCE tells the maximum distance the hand should go. It is assumed
that such a move will normally terminate by an ON test.
For example:
MOVE YELLOW
WITH VELOCITY=V1;
THROUGH T;
FOR DISTANCE=4;
ON FORCE(V1) > 2 DO STOP;
ENDM;
TASK STRUCTURES
An assembly task is often divided into subtasks which form a
partial order with respect to the order of execution. An example is
a task A which contains four subtasks, B, C, D, and E, of which B and
C must be done before D, and D must be done before E, but B and C
could be done in any order. It is possible in HAL to leave the
ordering of the subtasks up to the compiler, which will try to
optimise the entire operation. The way to specify the example just
given is as follows:
TASKBEGIN "A"
BEGIN "B"
<code for task B>
END "B";
BEGIN "C"
<code for task C>
END "C";
BEGIN "D AND E"
PREREQUISITE "B", "C";
<code for task D>
<code for task E>
END "D AND E"
END "A"
The word TASKBEGIN introduces a "task block", which contains
a set of statements. These statements may be blocks identified by
strings and may contain the declaration that certain other named
blocks within the same task are prerequisite for the inception of
this block.
The order in which the statements are performed is determined
only insofar as the prerequisite conditions demand. The compiler may
reorder them consistent with the preconditions, and may even execute
some of the statements simultaneously (as if there were a COBEGIN),
if this is feasible.
It is useful to place assertions at the beginning of the code
for any of the statements within a task; this assists the compiler in
maintaining its world model, and is also used to help decide the
proper ordering of the tasks. It is likewise good to place assertions
at the end of each statement.
LIBRARY ROUTINES
There is a library of useful routines which system
programmers have prepared. The expander is familiar with these
routines, and can insert them in the program wherever the user
wishes. For example, say there is a library routine called INSERT,
which needs to know screw-length and hole-location. After the screw
has been picked up, the user can code:
INSERT (SCREW_LENGTH = 3, HOLE_LOCATION = F1);
The word INSERT is recognized as a special word by the
parser, which has a table of currently defined library routines and
the names of their arguments.
Some library routines return values; these values may be
obtained by an assignment statement:
S1 ← COSINE (ANGLE = π/30);
To create a library routine, this format is followed:
MAKE_LIBRARY INSERT (SCALAR SCREW_LENGTH; FRAME HOLE_LOCATION);
<code for insert>
It is possible to invoke the library routine with arguments
arranged as for any other procedure call. In this case, the arguments
must correspond in order and type with those in the statement which
originally defined the routine. The advantage of allowing symbolic
names for the arguments is that the order need not be recalled. As
an example, the first call above could have been written:
INSERT (3, F1);
Yet another syntax is acceptable for invocation of library
routines; it is included for compatibility with high level primitives
(see the section on advanced language features). Here is how the
INSERT call would look:
INSERT
WITH HOLE_LOCATION F1
WITH SCREW_LENGTH 3;
The reserved word WITH introduces a clause which specifies a set of
parameters. It is possible to combine the two parameters into one
clause, like this:
INSERT
WITH HOLE_LOCATION F1, SCREW_LENGTH 3;
Some library routines have the restriction that the
parameters are expected to be names of variables, and not constants.
In the case that a constant is supplied, a warning will be printed,
and a temporary variable will be declared to hold the given constant.
IMPLEMENTATION OF ON-MONITORS
This page is a set of notes for the eventual documentation.
Author: RF
The code for an on-monitor is in-line and jumped around.
The code is PDP11 code, including calls on arithmetic subroutines,
if neccessary.
Each on-monitor is given a unique name by the compiler.
The scheduler associates that name with the location of the
enable bit for this monitor.
On-monitors have only two states: enabled and disabled.
There is a status word associated with each on-monitor;
it contains two bits: the enabled, and the kill.
At the start of a program, all on-monitors are disabled.
That means enable=0, kill=0.
At the end of a block, the compiler includes code to disable all
on-monitors within that block.
Only one copy of an on-monitor ever exists. No multiple instantiations.
ENABLE <NAME>
To enable a software monitor,
tell kernel to activate it at its address, in on-level, where
it will have PDP11 code to:
1) look at its "enabled bit". If already on, dismiss. If not, turn on.
2) clear all its working buffers (if any)
3) schedule its second entry (the monitor itself) to be awakened at
the appropriate intervals. Then dismiss.
The software monitor, every time it is awakened, does the following:
1) look at its "kill bit". If on, clear and dismiss.
2) execute the code for its check.
3) if condition satisfied, do immediate stuff,
instantiate the conclusion, and dismiss.
4) reschedule self at appropriate interval. Dismiss.
To enable a hardware monitor,
tell kernel to activate it at its address, in on-level, where
it will have PDP11 code to:
1) look at the device enabled bit. If already on, return. If not, turn on.
2) dismiss.
DISABLE <NAME>
To disable a software monitor, set its "kill bit".
To disable a hardware monitor, clear its "enabled bit".
INTERPRETER CODE FOR MOTIONS
not yet ready. Author: RF.
The code generated for a move includes the following interpreter commands:
1) Prepare move. This points to the move table, and causes the trajectory
to be modified to conform to current locations of via points. Joint
loading is calculated for each via point as well.
2) Enable on-monitor. There may be more than one of these instructions.
3) Start motion. This points to the modified move table.
4) Await completion. This causes the interpreter to wait until the
move has completed, or the arm has been stopped for some reason.
The signal which awakes the interpreter comes from whichever joint
servo finishes last: each servo, as it finishes, checks to see if all
the others are done, and if so, awakes the interpreter at servo level.
5) disable all on-monitors associated with move. This is done at
servo level
6) dismiss to interpreter level.
THE INTERPRETER SCHEDULER
The interpreter scheduler is a set of routines used for
handling interpreter sprouting, termination, waiting, and scheduling,
and for on-monitor enabling and disabling. The code "↑" means
switch to high priority (above any on-monitor); the code "↓" means
return to previous priority.
INTEGER ARRAY DEPENDENTS [0:MAXNAME];
COMMENT: Count of how many dependent interpreters each
named interpreter has;
INTEGER ARRAY MOTHER [0:MAXNAME];
COMMENT: Each interpreter has a mother;
RCLASS PROCESSID (INTEGER NAME, PC; RPTR(PROCESSID) NEXT);
RPTR (PROCESSID) CURRENT;
RCLASS QUEUE (RPTR(PROCESSID) HEAD, TAIL);
RPTR(QUEUE) RUNQUEUE, WAITQUEUE;
PROCEDURE ENQUEUE(RPTR(QUEUE) WHERE; RPTR(PROCESSID) WHO);
BEGIN
COMMENT: Puts a process on a queue. QUEUE tells which
queue. WHO is the process id;
QUEUE:TAIL(WHERE) ← PROCESSID:NEXT(QUEUE:TAIL(WHERE)) ← WHO;
END;
RPTR(PROCESSID) PROCEDURE DEQUEUE (RPTR(QUEUE) WHERE);
BEGIN
COMMENT: Returns head of queue, which is removed;
RPTR(PROCESSID) ANSWER;
ANSWER ← QUEUE:HEAD(WHERE);
QUEUE:HEAD(WHERE) ← PROCESSID:NEXT(QUEUE:HEAD(WHERE));
RETURN(ANSWER);
END;
INTEGER PROCEDURE GENNAME;
BEGIN
COMMENT: Assigns a new name for a procedure from a heap of
names. All procedure names are integers;
END;
PROCEDURE SPROUT (IPC);
BEGIN
COMMENT: IPC is the interpreter program counter.
The effect is to put the new interpreter in the run-queue;
INTEGER NEWNAME, MOTHERNAME;
RPTR (PROCESSID) WHO;
↑;
NEWNAME ← GENNAME;
WHO ← NEW_RECORD (PROCESSID);
PROCESSID:NAME(WHO) ← NEWNAME;
PROCESSID:PC(WHO) ← IPC;
MOTHER(NEWNAME) ← MOTHERNAME ← PROCESSID:NAME(CURRENT);
DEPENDENTS(MOTHERNAME) ← DEPENDENTS(MOTHERNAME) + 1;
ENQUEUE(RUNQUEUE,WHO);
↓;
END;
PROCEDURE DISMISS;
BEGIN COMMENT: Terminates the caller;
INTEGER VICTIM;
↑;
VICTIM ← PROCESSID:NAME(CURRENT);
IF DEPENDENTS(VICTIM) ≠ 0
THEN BEGIN COMMENT: This should never happen;
ERROR("ACTIVE PROCESS DEPENDS ON BLOCK BEING EXITED");
JOIN(VICTIM);
END;
DEPENDENTS(MOTHER(VICTIM)) ← DEPENDENTS(MOTHER(VICTIM)) - 1;
∀ ON | VICTIM⊗ON DO
DISABLE(ON);
CURRENT ← RNULL;
↓;
JRST RESCHEDULE;
END;
PROCEDURE JOIN;
BEGIN COMMENT: Caller wishes to
wait until all descendents have dismissed;
↑;
IF DEPENDENTS(PROCESSID:NAME(CURRENT)) ≠ 0
THEN BEGIN
PROCESSID:PC(CURRENT) ← <RETURN ADDRESS OF NAME>;
ENQUEUE(WAITQUEUE, CURRENT);
↓;
JRST RESCHEDULE;
END
ELSE ↓;
END;
PROCEDURE ENABLE (OPC, OSW);
BEGIN COMMENT: OPC is on-monitor
program counter, OSW is on-monitor status word;
↑;
MAKE NAME⊗OSW;
PUSHJ (OPC);
↓;
PROCEDURE DISABLE (OSW);
BEGIN COMMENT: OSW is on-monitor status word;
↑;
KILL(OSW) ← 1;
ENABLE(OSW) ← 0;
↓;
END;
PROCEDURE RESCHEDULE;
BEGIN
↑;
IF CURRENT = RNULL
THEN BEGIN
WHILE RUNQUEUE = RNULL DO BEGIN ↓; ↑; END;
CURRENT ← DEQUEUE(RUNQUEUE);
PUSH NAME(CURRENT);
PUSH IPC(CURRENT);
↓;
PUSHJ INTERPRETER;
END;
ELSE ↓;
END;
PROCEDURE SIGNAL (EVENT);
BEGIN COMMENT: EVENT is a unique name for this event;
∀ INTERPS | INTERPS⊗WAIT≡EVENT DO
BEGIN
DENY X⊗WAIT≡EVENT;
IF ¬ X⊗WAIT≡ANY
THEN BEGIN
ENQUEUE (RUNQUEUE,<X, WITH ITS INFORMATION>);
JRST RESCHEDULE;
END;
END;
END;
DEPROACHES
Many objects have shapes which necessitate care as the arm
approaches them or departs from them. HAL supplies one method for
insuring that every time the arm approaches a frame, it will pass
through some associated spot first, and every time it leaves that
frame, it passes once again through the same spot. The "spot" is
termed a DEPROACH (from DEParture and apPROACH), and is really a
transformation to be applied to the frame involved in order to
discover the appropriate place through which to pass. The reason that
a transformation is used, and not a frame itself, is that deproaches
are often used for large objects, like the table, and the proper
point to pass through in that case is 10 centimeters above where the
hand is meant to arrive on the table (or above where the hand
currently is on the table, if a departure is meant), not the point
10 centimeters above the table origin.
SPECIFICATION OF DEPROACHES
One specifies the deproach of an object by making an
assertion; for example, the deproach of the table is automatically
asserted as follows:
ASSERT FACT (DEPROACH TABLE [(0 0 10) | (0 0 0)]). This means
that the correct departure point from a spot F on the table will be
[(0 0 10) | (0 0 0)] * F.
As an object moves about in space, its deproach point moves
as well. Thus ASSERT FACT (DEPROACH FROB [(0 10 0) | (0 0 0)]) means
that no matter where the frob may go, its deproach will be 10
centimeters in the y-direction away from its origin, as measured in
its own coordinate system.
What if the hand is not at FROB, but wishes to use its
deproach? This also is handled correctly; the deproach point will be
10 centimeters in FROB's y-direction from wherever the hand actually
is.
Deproaches, being transformations, also have the power to
include rotations. These are considered to be rotations about the
origin of the coordinate system involved; the rotation occurs, as
usual, before translation. Thus
ASSERT FACT (DEPROACH FROB [(5 0 0) : (0 0 90)]) has the
effect that whenever FROB's deproach is used from any frame, the
deproach frame will be the given frame, rotated about FROB's origin
by 90 degrees in Z, and then translated 5 centimeters in FROB's X
direction. The purpose of including rotations is for completeness;
the user may seldom find them useful.
The way deproaches are implemented is this: Say that a frame
F is given deproach transformation D. It is desired to find the
frame which is the deproach point from some other frame H (for
example, where the hand is, for departure), using F's deproach. The
frame which is used is F * D * (F→TABLE) * H. This rather
complicated-looking expression has a particularly simple form if H =
F, because of the identities (F → TABLE) * F = TABLE, and X * TABLE =
X; using these, we get that F's own deproach point is F * D.
DEFAULT CHOICE OF DEPROACH
In approaching a frame, if that frame has a deproach, it is
used. If it does not, but is attached to some frame which does, then
that frame's deproach is used; if that frame has no deproach, then
the search continues up the ladder of attachment until some frame is
found with a deproach. If none at all is found, then the table's
deproach is used as a default. (One way to think of this is to
consider all frames ultimately attached to the table.) In aproaching
a frame which is merely the result of a calculation (such as MOVE
YELLOW TO FROB + (0 0 1)) the default deproach is null.
In departing from a frame, it matters whether that frame is
now attached to the hand or not. If not, then the same algorithm for
finding deproach is used as in the case of approach. But if the frame
has been detached from some erstwhile mother and is now attached to
the hand, then its old mother's deproach is used (and if there is
none, the same search is made). How can the compiler keep track of
the fact that the frame was once attached to something else? Whenever
the statement "DETACH frob FROM mother" is encountered, it generates
two assertions:
DENY FACT (ATTACHED frob mother);
ASSERT FACT (WAS_ATTACHED frob mother). Therefore, a frame
attached to the hand still has some "memory" of its previous state of
attachment.
EXPLICIT CHOICE OF DEPROACH
All of the automatic generation of deproach points can be
explicitly overriden in a MOVE or GO statement by means of the USING
clause. This can take two forms:
USING APPROACH = NIL; COMMENT: No approach point at all is
used;
USING APPROACH = DEPROACH(frob); COMMENT: frob's deproach is
used;
Note that the word APPROACH could be replaced by DEPROACH in
each of the examples above.